Fix overlay output-type picker: hoisted-popover content updates + drill-down#924
Conversation
Code Review — PR #924: Fix overlay output-type pickerOverviewFixes two independent bugs that made the overlay output-type picker unusable in 1.0 RC testing:
The solution replaces inline section expansion with an iPhone-style drill-down in a fixed frame, moves navigation state onto CorrectnessRoot cause fix is correct. Dropping Global monitor lifecycle looks right.
Issues & Suggestions1.
|
…ll-down The overlay mapper's output-type picker was unusable: clicking an option that revealed more choices (System Action / Launch App / Go to Layer) did nothing. Root-caused with instrumentation to the window-anchored popover host, plus a non-key-window click issue, and reworked the picker UX. Core fix — WindowAnchoredPopover: - WindowAnchoredPopoverEntry had identity-only Equatable (compared just `id`). SwiftUI used it to skip both the preference update and the content re-render when a same-id entry's *content* changed, so the host kept showing stale content — the picker never reflected taps even though the model and rebuilt content were correct. Removed Equatable; the ESC monitor and appear/disappear animation key on `entries.map(\.id)` instead. - Build popover content fresh on each emission rather than caching a one-shot AnyView at open (the cache froze the content's dependency on live state). - Dismiss on clicks outside the app's own windows via a global mouse-down monitor — the in-window backdrop can't see those, so the popover lingered when clicking onto another app or the desktop. First-click registration: - FirstMouseHostingView overrides acceptsFirstMouse so a click lands on the control under the cursor even while the non-focus-stealing overlay window isn't key (previously the activation click was swallowed). Picker UX (iPhone-style drill-down): - Replaced inline section expansion with a fixed-frame paged drill-down: the root list slides to a sub-page (System Action grid / app list / layer list) and back. A stable frame avoids the height-reflow that fought the hoisted positioning pipeline. - Moved the picker's navigation/selection state (page, selectedLayerOutput, launch-app search) onto MapperViewModel: the popover renders in the detached host where view @State doesn't propagate, but the shared @observable does. Launch App search: - Added a name filter to the app list. Uses AutoFocusTextField, an AppKit NSTextField that calls makeFirstResponder directly, because SwiftUI @focusstate does not bridge across the hoisted popover. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
8242982 to
cdd9a85
Compare
Code Review — Overlay Output-Type Picker FixSummary: Fixes an unresponsive output-type picker in the overlay mapper caused by two independent bugs, and replaces the inline-expand pattern with a fixed-frame drill-down. Root Cause Fix —
|
RC QA surfaced the overlay output-type picker being unclickable; root-caused to WindowAnchoredPopover's identity-only Equatable discarding content updates, fixed and reworked into a drill-down with Launch App search (#924). Verdict now reflects re-cutting the RC from master with the fix. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Dashboard: Friday morning state — all Thu work merged, today is notes + screenshots 6 of 10 gates closed. Thursday's two Neovim detail-page bugs (#903 wrong component, #908 sheet clipping) are on master. Verdict + schedule updated to Friday: gates 8 (notes) and 9 (screenshot pass) close today, ~1h total; gate 7 RC + gate 10 video on Sat. Countdown to 1 day. * Dashboard: fix system category count clobbered by countdown replace The Fri-AM countdown edit globally replaced <span class=num>2</span> and accidentally knocked the 'system' category bar from its (already wrong) 2 down to 1. Real count is 3 (macOS Function Keys, Leader Key, Fast Navigation); categories now sum to 22 again. Caught by codex + claude review on #910. * Dashboard: Saturday release-day state — #899 + #921 merged, cutting the RC 8 of 10 gates closed. Countdown to 0 (TODAY). Verdict → 'Shipping today': cmd-removal + screenshot cleanup merged after gating two runner flakes (#922). Gate 7 (RC) now in-progress; gate 9 closed (screenshots stripped/preserved via #921/#920); gate 8 finalizing with qualified import framing. Also fixes the system category count (3, not the review-flagged 1). * Dashboard: Saturday release-day — burndown, verdict, footer to current Burndown now plots actual through Sat (no leftover Thu projection): Sat is the current marker near the floor; ~3h of execution left (RC + smoke + publish), not feature work. Verdict reflects release-prep #923 merged (version → 1.0.0/build 4, appcast + squatting-tag cleanup) with the signed RC building. Footer timestamp Wed → Sat 2026-06-13; drops the overclaimed "auto-synced" wording (it's hand-maintained). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Dashboard: note overlay picker blocker found + fixed in RC QA (#924) RC QA surfaced the overlay output-type picker being unclickable; root-caused to WindowAnchoredPopover's identity-only Equatable discarding content updates, fixed and reworked into a drill-down with Launch App search (#924). Verdict now reflects re-cutting the RC from master with the fix. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
The overlay mapper's output-type picker was unusable: clicking an option that should reveal more choices (System Action / Launch App / Go to Layer) did nothing. Found during 1.0 RC manual testing.
Root cause (found via instrumentation)
WindowAnchoredPopoverEntryhad identity-onlyEquatable(compared justid). SwiftUI used it to skip both the preference update and the content re-render whenever a same-id entry's content changed — so the hoisted popover host kept displaying stale content. The tap fired, the model updated, and the content was even rebuilt correctly — the host just refused to show the update. (Logged trace confirmed:tap fired → page=systemActions → popover REBUILT page=systemActions, but the UI stayed on the root list.)A second, independent issue: the non-focus-stealing overlay window swallowed the first click as a window-activation event, so controls felt dead until the window was already key.
Changes
WindowAnchoredPopover(the core fix)Equatable; the ESC monitor and appear/disappear animation key onentries.map(\.id)instead.AnyViewat open (the cache froze its dependency on live state).First-click registration
FirstMouseHostingViewoverridesacceptsFirstMouseso the first click lands on the control under the cursor even while the overlay isn't key.Picker UX → iPhone-style drill-down
outputPickerPage,selectedLayerOutput, launch-app search) ontoMapperViewModel: the popover renders in the detached host where view@Statedoesn't propagate, but the shared@Observabledoes.Launch App search
AutoFocusTextField— an AppKitNSTextFieldthat callsmakeFirstResponderdirectly, because SwiftUI@FocusStatedoes not bridge across the hoisted popover.Testing
swift buildgreen; swiftformat (pinned 0.61.1) + swiftlint clean.Surfaced and fixed during the 1.0 release-candidate QA pass.